package com.yeziji.dataCenter.business.dataSource.service.impl;

import com.alibaba.druid.pool.DruidDataSource;
import com.mybatisflex.core.FlexGlobalConfig;
import com.mybatisflex.core.datasource.FlexDataSource;
import com.yeziji.common.CommonDataSourceConfig;
import com.yeziji.dataCenter.business.dataSource.service.DataCenterDataSourceService;
import com.yeziji.dataCenter.business.record.service.DataCenterMaxIdRecordService;
import com.yeziji.dataCenter.common.msg.DataCenterErrorMsg;
import com.yeziji.dataCenter.constant.DataCenterMaxIdTypeEnum;
import com.yeziji.dataCenter.constant.DataSourceTemplateEnum;
import com.yeziji.devops.annotation.DevopsUseDataSource;
import com.yeziji.devops.business.dataSource.dto.DevopsDataSourceOperateDTO;
import com.yeziji.devops.business.dataSource.entity.DevopsDataSourceEntity;
import com.yeziji.devops.business.dataSource.service.DevopsDataSourceService;
import com.yeziji.devops.sql.DevopsJdbc;
import com.yeziji.exception.ApiException;
import com.yeziji.utils.NanoIdUtils;
import com.yeziji.utils.expansion.Asserts;
import com.yeziji.utils.expansion.Str2;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.Statement;
import java.util.List;
import java.util.Set;

/**
 * 数据中心数据源管理服务 实现类
 *
 * @author hwy
 * @since 2024/10/26 13:43
 **/
@Slf4j
@DevopsUseDataSource
public class DataCenterDataSourceServiceImpl implements DataCenterDataSourceService {
    @Value("${yzj.data-center.ds.host:127.0.0.1}")
    private String host;
    @Value("${yzj.data-center.ds.port:4096}")
    private String port;
    @Resource
    private DevopsJdbc devopsJdbc;
    @Resource
    private DevopsDataSourceService devopsDataSourceService;
    @Resource
    private DataCenterMaxIdRecordService dataCenterMaxIdRecordService;

    @Override
    public String createDataSource(DevopsDataSourceOperateDTO operateDTO) {
        // creator 就是客户端标识
        DevopsDataSourceEntity sourceEntity = devopsDataSourceService.getByCreator(operateDTO.getCreator());
        if (sourceEntity != null) {
            // 返回云端标识
            return sourceEntity.getName();
        }
        final DataSourceTemplateEnum sourceTemplateEnum = DataSourceTemplateEnum.getByCode(operateDTO.getTemplateCode());
        int maxId = dataCenterMaxIdRecordService.getAsMaxIdByType(DataCenterMaxIdTypeEnum.CREATE_DATA_SOURCE) + 1;
        log.info("开始创建云端索引为: {} 的云库", maxId);
        final String databaseName = sourceTemplateEnum.getDatabaseName(Str2.formatNumber(maxId, 5));

        // 创建数据库, 创建之后直接 increase(无法通过事务控制)
        devopsJdbc.execute("create database " + databaseName);
        Asserts.isFalse(dataCenterMaxIdRecordService.increaseByType(DataCenterMaxIdTypeEnum.CREATE_DATA_SOURCE), DataCenterErrorMsg.INIT_CLOUD_DATABASE_FAILED);

        // 创建基本 template
        DataSource dataSource = Asserts.notNull(devopsJdbc.getDataSource(), DataCenterErrorMsg.INIT_CLOUD_DATABASE_FAILED);
        try (Connection connection = dataSource.getConnection(); Statement statement = connection.createStatement()) {
            // 读取模板库(这里一般是获取业务表以及业务初始化数据)
            String templateDataBase = sourceTemplateEnum.getTemplateBase();
            List<String> tables = devopsJdbc.getTables(templateDataBase);
            // 忽略指定的数据库(一般通用数据库在 base file 中统一指定)
            final Set<String> ignoreTables = sourceTemplateEnum.getIgnoreTables();
            for (String table : tables) {
                if (ignoreTables.stream().anyMatch(ignoreTable -> Str2.contain(ignoreTable, table, true))) {
                    continue;
                }
                statement.addBatch(String.format("create table `%s`.`%s` like `%s`.`%s`", databaseName, table, templateDataBase, table));
                statement.addBatch(String.format("insert into `%s`.`%s` select * from `%s`.`%s`", databaseName, table, templateDataBase, table));
            }
            statement.executeBatch();
            log.info("创建数据表成功!!!");
        } catch (Exception e) {
            log.error("创建数据表失败: {}", e.getMessage());
            devopsJdbc.execute("drop database " + databaseName); // drop database
            dataCenterMaxIdRecordService.decreaseByType(DataCenterMaxIdTypeEnum.CREATE_DATA_SOURCE);
            throw new ApiException(DataCenterErrorMsg.INIT_CLOUD_DATABASE_FAILED);
        } finally {
            devopsJdbc.execute("use yzj_data_center"); // 切换回 center
        }

        // 创建数据库用户
        String password = NanoIdUtils.randomNaoId(8);
        devopsJdbc.execute(String.format("CREATE USER '%s'@'%%' IDENTIFIED BY '%s';", databaseName, password));
        devopsJdbc.execute(String.format("GRANT ALL PRIVILEGES ON %s.* TO '%s'@'%%';", databaseName, databaseName));
        devopsJdbc.execute("FLUSH PRIVILEGES;");

        // 记录
        String dbName = NanoIdUtils.randomNotSymbolNaoId(8);
        DevopsDataSourceOperateDTO newDbOperate =
                DevopsDataSourceOperateDTO.builder()
                        .name(dbName)
                        // 通过外部传达进来的信息
                        .creator(operateDTO.getCreator())
                        .desc(operateDTO.getDesc())
                        // 数据源信息
                        .url(
                                String.format(
                                        "jdbc:mysql://%s:%s/%s?useSSL=false&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&allowMultiQueries=true",
                                        host, port, databaseName
                                )
                        )
                        .driverClassName("com.mysql.cj.jdbc.Driver")
                        .username(databaseName)
                        .password(password)
                        .build();
        if (devopsDataSourceService.saveOrUpdate(newDbOperate)) {
            // 加入 dataSource
            this.addDataSource(newDbOperate);
            return dbName;
        }
        throw new ApiException(DataCenterErrorMsg.INIT_CLOUD_DATABASE_FAILED);
    }

    /**
     * 将创建的数据源加入当前 flexDataSource 中
     *
     * @param operateDTO 操作数据源的信息
     */
    private void addDataSource(DevopsDataSourceOperateDTO operateDTO) {
        DruidDataSource initDruidDataSource = CommonDataSourceConfig.getInitDruidDataSource();
        initDruidDataSource.setUrl(operateDTO.getUrl());
        initDruidDataSource.setUsername(operateDTO.getUsername());
        initDruidDataSource.setPassword(operateDTO.getPassword());
        // 赋值数据源
        FlexDataSource dataSource = FlexGlobalConfig.getDefaultConfig().getDataSource();
        dataSource.addDataSource(operateDTO.getName(), initDruidDataSource);
    }
}